/*
 * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.lidroid.xutils;

import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.lidroid.xutils.util.LogUtils;
import com.lidroid.xutils.view.EventListenerManager;
import com.lidroid.xutils.view.ResLoader;
import com.lidroid.xutils.view.ViewFinder;
import com.lidroid.xutils.view.ViewInjectInfo;
import com.lidroid.xutils.view.annotation.ContentView;
import com.lidroid.xutils.view.annotation.FragView;
import com.lidroid.xutils.view.annotation.PreferenceInject;
import com.lidroid.xutils.view.annotation.ResInject;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.lidroid.xutils.view.annotation.event.EventBase;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ViewUtils {
	
	private ViewUtils() {
	}
	
	public static void inject(View view) {
		injectObject(view, new ViewFinder(view));
	}
	
	public static void inject(Activity activity) {
		injectObject(activity, new ViewFinder(activity));
	}
	
	public static void inject(PreferenceActivity preferenceActivity) {
		injectObject(preferenceActivity, new ViewFinder(preferenceActivity));
	}
	
	/**
	 * A shortcut of {@link #inject(android.support.v4.app.Fragment)}, the
	 * second
	 * argument was null as default.
	 * 
	 * @param fragment
	 *            fragment to inject, may not be null
	 * @return fragment's content view.Never will it be null
	 */
	public static View inject(@NonNull android.support.v4.app.Fragment fragment) {
		return inject(fragment, (ViewGroup) null);
	}
	
	/**
	 * Bind fragment by annotation.Be aware, this is only for
	 * <b>android.support.v4.app.Fragment</b>. You are recommended to call this
	 * in
	 * {@link android.support.v4.app.Fragment#onCreateView(LayoutInflater, ViewGroup, android.os.Bundle)}
	 * as the result;
	 * <p/>
	 * <br/>
	 * <br/>
	 * If fragment has no view, will inflate a view declare by {@link FragView},
	 * bind it and return the view; <br/>
	 * <br/>
	 * Otherwise, only bind the fragment's view and return;
	 * 
	 * @param fragment
	 *            fragment to inject, may not be null
	 * @param container
	 *            container of the fragment's view,null is OK here
	 * @return fragment's content view.Never will it be null
	 * @throws IllegalStateException
	 *             Call this method before fragment has a view and did
	 *             not set a {@link FragView} to declare the res, or the res
	 *             was not available to inflate a view, will
	 *             throw this exception.
	 */
	public static View inject(@NonNull android.support.v4.app.Fragment fragment, @Nullable ViewGroup container) {
		View view = fragment.getView();
		
		// No view in fragment, just inflate one
		if (view == null) {
			Class<?> clazz = fragment.getClass();
			FragView fragView = clazz.getAnnotation(FragView.class);
			if (fragView != null) {
				view = LayoutInflater.from(fragment.getActivity()).inflate(fragView.value(), container, false);
			}
		}
		if (view == null) {
			throw new IllegalStateException(
					"Fragment has no view and is unable to inflate one. Have you set a available res as FragView to this fragment ? ");
		}
		
		// inject the view
		injectObject(fragment, new ViewFinder(view));
		return view;
	}
	
	/**
	 * A shortcut of {@link #inject(android.app.Fragment)}, the
	 * second argument was null as default.
	 * 
	 * @param fragment
	 *            fragment to inject, may not be null
	 * @return fragment's content view.Never will it be null
	 */
	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public static View inject(@NonNull android.app.Fragment fragment) {
		return inject(fragment, (ViewGroup) null);
	}
	
	/**
	 * Bind fragment by annotation.Be aware, this is only for
	 * <b>android.app.Fragment</b>.Technically, when you use this method means
	 * your sdk used in project is {@link Build.VERSION_CODES#HONEYCOMB} or
	 * higher.You are recommended to call this in
	 * {@link android.app.Fragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)}
	 * as the result;
	 * <br/>
	 * <br/>
	 * If fragment has no view, will inflate a view declare by {@link FragView},
	 * bind it and return the view; <br/>
	 * <br/>
	 * Otherwise, only bind the fragment's view and return;
	 * 
	 * @param fragment
	 *            fragment to inject, may not be null
	 * @param container
	 *            container of the fragment's view,null is OK here
	 * @return fragment's content view.Never will it be null
	 * @throws IllegalStateException
	 *             Call this method before fragment has a view and did
	 *             not set a {@link FragView} to declare the res, or the res
	 *             was not available to inflate a view, will
	 *             throw this exception.
	 */
	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public static View inject(@NonNull android.app.Fragment fragment, @Nullable ViewGroup container) {
		View view = fragment.getView();
		
		// No view in fragment, just inflate one
		if (view == null) {
			Class<?> clazz = fragment.getClass();
			FragView fragView = clazz.getAnnotation(FragView.class);
			if (fragView != null) {
				view = LayoutInflater.from(fragment.getActivity()).inflate(fragView.value(), container, false);
			}
		}
		if (view == null) {
			throw new IllegalStateException(
					"Fragment has no view and is unable to inflate one. Have you set a available res as FragView to this fragment ? ");
		}
		
		// inject the view
		injectObject(fragment, new ViewFinder(view));
		return view;
	}
	
	public static void inject(Object handler, View view) {
		injectObject(handler, new ViewFinder(view));
	}
	
	public static void inject(Object handler, Activity activity) {
		injectObject(handler, new ViewFinder(activity));
	}
	
	public static void inject(Object handler, PreferenceGroup preferenceGroup) {
		injectObject(handler, new ViewFinder(preferenceGroup));
	}
	
	public static void inject(Object handler, PreferenceActivity preferenceActivity) {
		injectObject(handler, new ViewFinder(preferenceActivity));
	}
	
	private static void injectObject(Object handler, ViewFinder finder) {
		
		Class<?> handlerType = handler.getClass();
		
		// inject ContentView
		ContentView contentView = handlerType.getAnnotation(ContentView.class);
		if (contentView != null) {
			try {
				Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
				setContentViewMethod.invoke(handler, contentView.value());
			} catch (Throwable e) {
				LogUtils.e(e.getMessage(), e);
			}
		}
		
		// inject view
		Field[] fields = handlerType.getDeclaredFields();
		if (fields != null && fields.length > 0) {
			for (Field field : fields) {
				ViewInject viewInject = field.getAnnotation(ViewInject.class);
				if (viewInject != null) {
					try {
						View view = finder.findViewById(viewInject.value(), viewInject.parentId());
						if (view != null) {
							field.setAccessible(true);
							field.set(handler, view);
						}
					} catch (Throwable e) {
						LogUtils.e(e.getMessage(), e);
					}
				} else {
					ResInject resInject = field.getAnnotation(ResInject.class);
					if (resInject != null) {
						try {
							Object res = ResLoader.loadRes(resInject.type(), finder.getContext(), resInject.id());
							if (res != null) {
								field.setAccessible(true);
								field.set(handler, res);
							}
						} catch (Throwable e) {
							LogUtils.e(e.getMessage(), e);
						}
					} else {
						PreferenceInject preferenceInject = field.getAnnotation(PreferenceInject.class);
						if (preferenceInject != null) {
							try {
								Preference preference = finder.findPreference(preferenceInject.value());
								if (preference != null) {
									field.setAccessible(true);
									field.set(handler, preference);
								}
							} catch (Throwable e) {
								LogUtils.e(e.getMessage(), e);
							}
						}
					}
				}
			}
		}
		
		// inject event
		Method[] methods = handlerType.getDeclaredMethods();
		if (methods != null && methods.length > 0) {
			for (Method method : methods) {
				Annotation[] annotations = method.getDeclaredAnnotations();
				if (annotations != null && annotations.length > 0) {
					for (Annotation annotation : annotations) {
						Class<?> annType = annotation.annotationType();
						if (annType.getAnnotation(EventBase.class) != null) {
							method.setAccessible(true);
							try {
								// ProGuard：-keep class * extends
								// java.lang.annotation.Annotation { *; }
								Method valueMethod = annType.getDeclaredMethod("value");
								Method parentIdMethod = null;
								try {
									parentIdMethod = annType.getDeclaredMethod("parentId");
								} catch (Throwable e) {
								}
								Object values = valueMethod.invoke(annotation);
								Object parentIds = parentIdMethod == null ? null : parentIdMethod.invoke(annotation);
								int parentIdsLen = parentIds == null ? 0 : Array.getLength(parentIds);
								int len = Array.getLength(values);
								for (int i = 0; i < len; i++) {
									ViewInjectInfo info = new ViewInjectInfo();
									info.value = Array.get(values, i);
									info.parentId = parentIdsLen > i ? (Integer) Array.get(parentIds, i) : 0;
									EventListenerManager.addEventMethod(finder, info, annotation, handler, method);
								}
							} catch (Throwable e) {
								LogUtils.e(e.getMessage(), e);
							}
						}
					}
				}
			}
		}
	}
	
}
